﻿using System;
using System.Collections.Generic;
using DDDProjectSample.Domain.Repositories;
using DDDProject.Aspects;
using DDDProject.Components;
using DDDProjectSample.Domain;

namespace DDDProjectSample.Application.Controllers
{
    /// <summary>
    /// Controller for Use Case "Forums management"
    /// </summary>
    public class ForumsController : DomainControllerBase
    {
        // ------------------------------------------------------------------------------------
        // Here we declare private fields that will holds references to repository components.
        // The controller uses repositories to load domain objects from the database
        // (or another data storage type of your choice, depending of the implementation of 
        // your repositories).
        // We decorate these fields with the [InjectComponent] aspect, in order to 
        // auto-initialize them at runtime.
        // 
        // Note that a repository is designed according to the following pattern :
        //      1) An interface is defined and placed into the "Repositories" 
        //         folder of the domain layer. This interface contains all methods'
        //         signatures of the repository (For example LoadById(long id) or
        //         Save(MyDomainObject obj), etc.).
        //      2) The repository's implementation is a class that implements the
        //         interface, and that is placed in the "Repositories" folder of the
        //         Infrastructure layer.
        //         To implement our repository, we use the NHibernate O/R mapper, which 
        //         is actually one of the most powerful O/R mapper on the market. You can
        //         look at the Infrastructure layer to see in details how repositories are 
        //         implemented.
        //      3) The repository component must be registered in the "DomainRepositories.config" 
        //         file of the Infrastructure layer. This enable the runtime injection of the 
        //         component in the controller thanks to the [InjectComponent] aspect.
        // 
        // The design of the repositories is driven by the domain model : Generally, we 
        // define one repository for each domain entity (ie. ForumRepository, TopicRepository, 
        // MessageRepository...). As its the domain model that defines what methods we 
        // need in the repositories, its the domain model that contains repositories' 
        // interfaces. The use of interfaces is needed in order to have low 
        // coupling between domain layer and infrastructure layer.
        // 
        // Note also that by using this design, you can change the implementations of your 
        // repositories without touching your domain layer (Just create new classes in the
        // infrastructure layer, and modify your "DomainRepositories.config" file).
        // -----------------------------------------------------------------------------------
        [InjectComponent]
        private IForumRepository _forumRepository;

        [InjectComponent]
        private ITopicRepository _topicRepository;

        [InjectComponent]
        private IMessageRepository _messageRepository;

        [InjectComponent]
        private IMemberRepository _memberRepository;

        /// <summary>
        /// Creates a new instance of class ForumsController
        /// </summary>
        public ForumsController()
        {
        }

        /// <summary>
        /// Lists all the forums managed by the system
        /// </summary>
        /// <returns>List of forums</returns>
        public ICollection<Forum> GetForums()
        {
            return _forumRepository.FindAll();
        }

        /// <summary>
        /// Lists all the topics of a forum
        /// </summary>
        /// <param name="forumId">Id of the forum</param>
        /// <returns>List of topics, ordered by </returns>
        public ICollection<Topic> GetTopics(long forumId)
        {
            return _topicRepository.FindByForumId(forumId);
        }

        /// <summary>
        /// Lists all the messages of a topic
        /// </summary>
        /// <param name="topicId">Id of the topic</param>
        /// <returns>List of messages</returns>
        public ICollection<Message> GetMessages(long topicId)
        {
            return _messageRepository.FindByTopicId(topicId);
        }

        /// <summary>
        /// Creates a new topic
        /// </summary>
        /// <param name="title">Title of the topic</param>
        /// <param name="forumId">Id of the forum that the topic belongs to</param>
        /// <param name="authorLogin">Login of the author of the topic</param>
        /// <param name="messageText">Text of the first message of the topic</param>
        /// <returns>The new created topic</returns>
        [UnitOfWork]
        public Topic CreateTopic(
            String title,
            long forumId,
            String authorLogin,
            String messageText)
        {
            // ---------------------------------------------------------------------------
            // Here we have the business code associated the "CreateTopic" system event.
            // 
            // The controller must not contain all the business code that treats 
            // the system event. ie, they must transmit the treatment at some real domain 
            // entities. Infact before coding, you must affect responsibilities onto your 
            // domain objects (Trace some UML sequence diagramm for this).
            // 
            // For example here, we have to create a topic. Responsibility affectation is
            // like asking the following question : "Which domain object is responsible 
            // of the topic creation ?". As forums contains the topics, we can reasonably
            // considered that the forum is responsible of topic creation. This decision
            // makes that we will have a "AddTopic" method onto our forum object.
            // 
            // Then we must ask ourselves "The forum will create the new topic, but which
            // object is responsible to create the first message of the topic ?". As 
            // before, the solution is simple : topics contains messages, so it's the 
            // topic that will have the responsibility to create a message. This decision
            // makes that we will have a "AddMessage" method onto our topic object.
            // 
            // So here we finally have the following sequence of instructions :
            //      1) We first loads the forum entity, on which we want to add a new 
            //         topic (See explanations about repositories on top of this file).
            //      2) We loads the user that will be the author of topic.
            //      3) We ask the forum to create the new topic, passing to it the
            //         topic's title, the topic's author and the first message to be
            //         created with the topic. The forum will create the new topic, and
            //         then ask the topic to create the first message, passing to it the
            //         message's text and message's author.
            //         For details, see the "AddTopic" method and the "AddMessage" one 
            //         into the Forum and Topic classes.
            //      4) We return the newly created topic.
            // 
            // This kind of design enables us to have a strong cohesion onto our domain
            // objects and methods. It enables a good separation of concern and good
            // responsibility affectations. All of this enable a good domain design !
            // 
            // Finally, you can notice the use of the [UnitOfWork] attribute on the
            // method declaration. This enables to automatically treat all the business 
            // logic played here as a unit of work (ie. if the server crashes in the
            // middle the instructions or if you launch an exception in your code, all 
            // work made against the end-database will be automatically rollbacked).
            // Note that using the [UnitOfWork] aspect does not mean that a transaction is
            // automatically opened against the end database just before entering the
            // current method. The transaction will only be automatically opened the first 
            // time a database access will ne made.
            // ---------------------------------------------------------------------------

            // Loads the forum
            Forum forum = _forumRepository.Get(forumId);

            // Loads the author
            Member author = _memberRepository.GetByLogin(authorLogin);

            // Adds the new topic to the forum
            Topic topic = forum.AddTopic(title, author, messageText);

            return topic;
        }

        /// <summary>
        /// Creates a new message
        /// </summary>
        /// <param name="text">Text of the message</param>
        /// <param name="topicId">Id of the topic that the message belongs to</param>
        /// <param name="authorLogin">Login of the author of the message</param>
        /// <returns>The new created message</returns>
        [UnitOfWork]
        public Message CreateMessage(
            String text,
            long topicId,
            String authorLogin)
        {
            // Loads the topic
            Topic topic = _topicRepository.Get(topicId);

            // Loads the author
            Member author = _memberRepository.GetByLogin(authorLogin);

            // Adds the new message to the topic
            Message message = topic.AddMessage(text, author);

            return message;
        }
    }
}
